
import {squash} from "./util.js";

function isUpperCaseChar(c) {
    return c.length === 1 && c === c.toUpperCase() && c !== c.toLowerCase();
}

/**
 * @param {string} type event type
 * @param {string} key the key property from the KeyboardEvent
 * @param {number} which the browser-specific numeric key code
 * @param {string} str the string representation of the numeric key code
 * @returns {string} the normalized 'key' value.
 */
function normalize(type, key, which, str) {
    if (key) {
        switch (key) {
            case "Down":  return "ArrowDown";
            case "Esc":   return "Escape";
            case "Left":  return "ArrowLeft";
            case "Right": return "ArrowRight";
            case "Up":    return "ArrowUp";
        }
        return key;  // Use the key property if it exists.
    }
    switch (which) {
        case 27: return "Escape";
        case 37: return "ArrowLeft";
        case 38: return "ArrowUp";
        case 39: return "ArrowRight";
        case 40: return "ArrowDown";
    }
    if (type === "keypress" && str) {
        return str;  // The string representation is acceptable for keypress events. For keyup and keydown, it is not.
    }
    return "NYI";
}

/**
 * @param {string} key the key property from the KeyboardEvent
 * @param {boolean} ctrlKey true if control pressed
 * @param {boolean} altKey true if alt pressed
 * @param {boolean} shiftKey true if shift pressed
 * @param {boolean} metaKey true if meta pressed
 * @returns {string} the key prefixed by modifiers delimited by '-'.
 *          Examples: "v", "Control-v", "Control-V", "Shift-Meta-ArrowLeft", "Shift-Meta-v", "Control-Meta-V"
 */
function chordify(key, ctrlKey, altKey, shiftKey, metaKey) {
    const chord = [];

    if (ctrlKey) {
        chord[0] = "Control";
    }
    if (altKey) {
        chord[1] = "Alt";
    }
    if (shiftKey && !isUpperCaseChar(key)) {
        // One-char upper case keys already encode presence of the Shift modifier in their value.
        chord[2] = "Shift";
    }
    if (metaKey) {
        chord[3] = "Meta";
    }
    if (key !== "Control" && key !== "Alt" && key !== "Shift" && key !== "Meta") {
        // Add key as long as it's not itself a modifier.
        chord[4] = key;
    }

    return chord.filter(e => e !== undefined).join("-");
}

/**
 * @param {KeyboardEvent} event the keyboard keyup, keypress, or keydown event.
 * @return {string} the key chord consisting of modifier keys and the pressed key delimited with '-' characters.
 *          Examples: "v", "Control-v", "Control-V", "Shift-Meta-ArrowLeft", "Shift-Meta-v", "Control-Meta-V"
 */
export function chord(event) {
    const {type, key, ctrlKey, altKey, shiftKey, metaKey} = event;
    const which = +event.which, str = String.fromCharCode(which);  // UNDONE: '+which' is correct here? What about "0"?
    const normalized = normalize(type, key, which, str);
    const chord = chordify(normalized, ctrlKey, altKey, shiftKey, metaKey);
    // console.log({type, chord, normalized, ctrlKey, altKey, shiftKey, metaKey, key, which, str});
    return chord;
}

/**
 * @param {Function} callback a function that takes four parameters: ƒ({string}chord, d, i, nodes). The first is the
 *        keyboard chord as identified by the chord function. The remainder are the standard D3 event callback
 *        arguments. The callback should return true if the keyboard event is handled and the default behavior should
 *        be prevented.
 * @returns {Function} a handler for D3 keyboard events:  selection.on("keydown", handleAsChord(chord => ...));
 */
export function handleAsChord(callback) {
    return function(/** @type {KeyboardEvent} */ event) {
        // Ignore this event if it has already been handled or is part of an IME composing operation.
        if (event.defaultPrevented || event.isComposing || event.keyCode === 229) {
            return;
        }
        if (callback.call(this, chord(event), ...arguments)) {
            squash(event);
        }
    }
}
